/*
 * %c character 
 * %d signed integers 
 * %i signed integers 
 * %e scientific notation, with a lowercase "e" 
 * %E scientific notation, with a uppercase "E" 
 * %f floating point 
 * %o octal 
 * %s a string of characters 
 * %X unsigned hexadecimal, with uppercase letters 
 * %% a '%' sign 
 */
function sprintf ()
{
	if (arguments.length == 0) return "";
	var params = [];
	for (var i = 0; i < arguments.length; i ++) params [i] = arguments [i];

	var pattern = params.shift ();
	var p = new StringBuilder (pattern, params);
	var ret = p.format ();
	delete p;
	return ret;
}

if (!String.prototype.trim)
{
	String.prototype.trim = function ()
	{
		return this.replace (/^[\s]*/, '').replace (/[\s]*$/, '');
	}
}

function StringBuilder (pattern, params)
{
	var pos = 0;
	var ret = "";

	this.format = function () 
	{
		for (; pos < pattern.length; pos ++)
		{
			var ch = pattern.charAt (pos);
			if (ch == '%')
			{
				var p = FindPattern ();
				ret += p.parse (params.shift ());
				delete p;
			}
			else ret += ch;
		}
		return ret;
	}

	function FindPattern ()
	{
		var p = "";
		for (pos ++; pos < pattern.length; pos ++)
		{
			var ch = pattern.charAt (pos);
			p += ch;
			if (isBorder (ch)) return new Pattern (p);;
		}

		return new Pattern (p);
	}
}

var letter = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
var LETTER = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
var X = ['c', 'd', 'i', 'e', 'E', 'f', 'o', 's', 'u', 'x', 'X', '%'];

function isBorder (ch)
{
	for (var i = 0; i < X.length; i ++) if (ch == X[i]) return true;
	return false;
}

function Pattern (format)
{
	this.length = 0;
	this.precision = 0;
	this.align = 'left';
	this.filling = "&nbsp;";
	var self = this;

	var type = format.charAt (format.length - 1);
	if (!isBorder (type)) throw "Unknown format : " + format;
	var s = format.substring (0, format.length - 1);

	if (s.charAt (0) == '0') self.filling = '0';

	if (type == 'c' || type == 's' | type == '%')
	{
		if (s.length > 0) throw "Parse pattern: %" + format + " error";
	}
	else
	{
		if (s.charAt (0) == '-')
		{
			this.align = 'left';
			s = s.substring (1);
		}
		else
		{
			this.align = "right";
		}
		var a = s.split ('.');
		this.length = parseInt (a [0], 10);
		if (a.length == 2) this.precision = a [1];
	}

	this.toString = function () 
	{
		return "[pattern:%" + format + ",length:" + this.length + ",precision:" + 
		this.precision + ",align:" + this.align + "]";
	}

	this.parse = function (value)
	{
		if (type == '%') return '%';
		if (type == 'c') return value.toString ().charAt (0);
		if (type == 's') return value.toString ();
		if (type == 'i' || type == 'd') return processInteger (value);
		if (type == 'f') return processFloat (value, false);
		if (type == 'u') return processFloat (value, true);
		if (type == 'e' || type == 'E') return processExpress (value, type);
		if (type == 'o') return processOctal (value);
		if (type == 'x' || type == 'X') return processHex (value, type);
	}

	function processInteger (v)
	{
		var value = parseInt (v);
		if (isNaN (value) || value != v) throw "Can't convert " + v + " to integer";
		var ret = value.toString ();
		if (self.length == 0 || ret.length > self.length) return ret;
		return append (ret, self.length - ret.length);
	}


	function processFloat (s, u)
	{
		var value = parseFloat (s);
		if (isNaN (value) || value != s) throw "Cant' convert " + s + " to float";
		return formatFloat (value, u);
	}

	function formatFloat (value, u)
	{
		if (u)
		{
			var y = 1;
			for (var i = 0; i < self.precision; i ++) y *= 10;
			value = (value * y + .5) / y;
		}
		var ret = value.toString ();
		if (self.length == 0 || ret.length - 1 > self.length)
		{
			if (self.precision == 0) return ret;
			var a = ret.split ('.');
			if (a [1].length > self.precision) a[1] = a [1].substring (0, self.precision);
			ret = a[0] + "." + a[1];
			return append (ret, self.length - ret.length);
		}
		else
		{
			var a = ret.split ('.');
			if (a.length == 1) a.push ("0");
			if (self.precision != 0) 
			{
				if (self.precision < a[1].length)
					a [1] = a[1].substring (0, self.precision);
				else 
				{
					var delta = self.precision - a[1].length;
					for (var i = 0; i < delta; i ++) a[1] += "0";
				}
				ret = a[0] + "." + a[1];
			}			
			return append (ret, self.length - ret.length);
		}
	}

	function processExpress (v, t)
	{
		var m = '+';
		var count = 0;
		var value = parseFloat (v);
		if (isNaN (value) || value != v) throw "Can't convert " + v + " to float";
		if (value > 1)
		{
			while (value >= 10) 
			{
				value /= 10;
				count ++;
			}
		}
		else
		{
			m = '-';
			while (value < 1)
			{
				value *= 10;
				count ++;
			}
		}
		var ret = formatFloat (value).trim ();
		return ret + t + m + count;
	}

	function processOctal (v)
	{
		var value = parseInt (v);
		if (isNaN (value) || value != v) throw "Can't convert " + v + " to integer";
		var ret = [];
		while (n > 0x07)
		{
			ret.push (n & 0x07);
			n >>= 3;
		}
		ret.push (n);
		ret.reverse ();
		return processInteger (ret.join (""));
	}

	function processHex (v, t)
	{
		var n = parseInt (v, 10);
		if (isNaN (n) || n != v) throw "Can't convert " + v + " to integer";

		var ret = [];
		while (n > 0x0f)
		{
			ret.push (n & 0x0f);
			n >>= 4;
		}
		ret.push (n);
		ret = ret.reverse ();
		var s = "";
		for (var i = 0; i < ret.length; i ++) {
			s += t == 'x' ? letter [ret[i]] : LETTER [ret[i]];
		}
		if (self.length == 0 || s.length > self.length) return s;
		var delta = self.length - s.length;
		if (self.align == 'right') 
			for (var i = 0; i < delta; i ++) s = self.filling + s;
		else
			for (var i = 0; i < delta; i ++) s += "&nbsp;";
		return s;
	}

	function append (value, count)
	{
		var ret = value;
		for (var i = 0; i < count; i ++)
		{
			if (self.align == 'left') ret += '&nbsp';
			else ret = self.filling + ret;
		}
		return ret;
	}

	this.type = type;
}